package cn.yx.common.security.webmvc.configuration;

import cn.yx.common.security.properties.SecurityProperties;
import cn.yx.common.security.service.JwtUserDetailsService;
import cn.yx.common.security.webmvc.access.CustomInvocationSecurityMetadataSource;
import cn.yx.common.security.webmvc.filter.JwtAuthenticationFilter;
import cn.yx.common.security.webmvc.filter.VerifyCodeAuthenticationFilter;
import cn.yx.common.security.webmvc.handler.CustomAccessDeniedHandler;
import cn.yx.common.security.webmvc.handler.CustomAuthenticationFailureHandler;
import cn.yx.common.security.webmvc.handler.CustomAuthenticationSuccessHandler;
import cn.yx.common.security.webmvc.manager.CustomAccessDecisionManager;
import cn.yx.common.security.webmvc.sms.SmsCodeAuthenticationFilter;
import cn.yx.common.security.webmvc.sms.SmsCodeAuthenticationProvider;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

/**
 * <p>SecurityConfiguration配置类</p>
 *
 * @author Wgssmart
 */
@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityConfiguration {

    private final SecurityProperties securityProperties;

    @Resource
    private JwtUserDetailsService jwtUserDetailsService;

    @Resource
    private CustomInvocationSecurityMetadataSource customInvocationSecurityMetadataSource;

    @Resource
    private CustomAccessDecisionManager customAccessDecisionManager;

    @Resource
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Resource
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    @Resource
    private CustomAccessDeniedHandler customAccessDeniedHandler;

    @Resource
    @Lazy
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Resource
    @Lazy
    private VerifyCodeAuthenticationFilter verifyCodeAuthenticationFilter;

    @Resource
    private SmsCodeAuthenticationProvider smsCodeAuthenticationProvider;

    @Resource
    @Lazy
    private AuthenticationManager authenticationManager;

    public SecurityConfiguration(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.ignoring().antMatchers("/", "/wx-auth/**");
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        // smsCodeAuthenticationFilter不能直接使用@Autowired注解，否则authenticationManager未设置会报错
        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter(securityProperties);
        smsCodeAuthenticationFilter.setAuthenticationManager(authenticationManager);
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
        return httpSecurity.authenticationProvider(smsCodeAuthenticationProvider)
                .addFilterBefore(jwtAuthenticationFilter, VerifyCodeAuthenticationFilter.class)
                .addFilterBefore(verifyCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .cors()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(customInvocationSecurityMetadataSource);
                        o.setAccessDecisionManager(customAccessDecisionManager);
                        return o;
                    }
                })
                .and()
                .formLogin()
                .loginPage(securityProperties.getAuthRequireUrl())
                .loginProcessingUrl(securityProperties.getLoginProcessUrl())
                .permitAll()
                .successHandler(customAuthenticationSuccessHandler)
                .failureHandler(customAuthenticationFailureHandler)
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .logout()
                .permitAll()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(customAccessDeniedHandler)
                .and()
                .userDetailsService(jwtUserDetailsService)
                .csrf()
                .disable()
                .build();
    }

}
